summaryrefslogtreecommitdiff
path: root/app/api/data-room/[projectId]/[fileId]/download/route.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/data-room/[projectId]/[fileId]/download/route.ts')
-rw-r--r--app/api/data-room/[projectId]/[fileId]/download/route.ts246
1 files changed, 246 insertions, 0 deletions
diff --git a/app/api/data-room/[projectId]/[fileId]/download/route.ts b/app/api/data-room/[projectId]/[fileId]/download/route.ts
new file mode 100644
index 00000000..3a3a8fdd
--- /dev/null
+++ b/app/api/data-room/[projectId]/[fileId]/download/route.ts
@@ -0,0 +1,246 @@
+// app/api/data-room/[projectId]/[fileId]/download/route.ts
+import { NextRequest, NextResponse } from 'next/server';
+import { getServerSession } from 'next-auth/next';
+import { authOptions } from '@/app/api/auth/[...nextauth]/route';
+import { FileService, type FileAccessContext } from '@/lib/services/fileService';
+import { promises as fs } from 'fs';
+import path from 'path';
+import db from "@/db/db";
+import { fileItems } from "@/db/schema/fileSystem";
+import { eq } from "drizzle-orm";
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: { projectId: string; fileId: string } }
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return NextResponse.json({ error: '인증이 필요합니다' }, { status: 401 });
+ }
+
+ const context: FileAccessContext = {
+ userId: Number(session.user.id),
+ userDomain: session.user.domain || 'partners',
+ userEmail: session.user.email,
+ ipAddress: request.ip || request.headers.get('x-forwarded-for') || undefined,
+ userAgent: request.headers.get('user-agent') || undefined,
+ };
+
+ const fileService = new FileService();
+
+ // 파일 접근 권한 확인
+ const hasAccess = await fileService.checkFileAccess(
+ params.fileId,
+ context,
+ 'download'
+ );
+
+ if (!hasAccess) {
+ return NextResponse.json(
+ { error: '파일 다운로드 권한이 없습니다' },
+ { status: 403 }
+ );
+ }
+
+ // FileService를 통해 파일 정보 가져오기 (다운로드 카운트 증가 및 로그 기록)
+ const file = await fileService.downloadFile(params.fileId, context);
+
+ if (!file) {
+ return NextResponse.json(
+ { error: '파일을 찾을 수 없습니다' },
+ { status: 404 }
+ );
+ }
+
+ // 파일 경로 확인
+ if (!file.filePath) {
+ return NextResponse.json(
+ { error: '파일 경로가 없습니다' },
+ { status: 404 }
+ );
+ }
+
+ // 실제 파일 경로 구성
+ const nasPath = process.env.NAS_PATH || "/evcp_nas";
+ const isProduction = process.env.NODE_ENV === "production";
+
+ let absolutePath: string;
+ if (isProduction) {
+ // 프로덕션: NAS 경로 사용
+ const relativePath = file.filePath.replace('/api/files/', '');
+ absolutePath = path.join(nasPath, relativePath);
+ } else {
+ // 개발: public 폴더 사용
+ absolutePath = path.join(process.cwd(), 'public', file.filePath);
+ }
+
+ // 파일 존재 여부 확인
+ try {
+ await fs.access(absolutePath);
+ } catch (error) {
+ console.error('파일을 찾을 수 없습니다:', absolutePath);
+ return NextResponse.json(
+ { error: '파일을 찾을 수 없습니다' },
+ { status: 404 }
+ );
+ }
+
+ // 파일 읽기
+ const fileBuffer = await fs.readFile(absolutePath);
+
+ // MIME 타입 결정
+ const mimeType = getMimeType(file.name, file.mimeType);
+
+ // 파일명 인코딩 (한글 등 특수문자 처리)
+ const encodedFileName = encodeURIComponent(file.name);
+
+ // Response Headers 설정
+ const headers = new Headers();
+ headers.set('Content-Type', mimeType);
+ headers.set('Content-Length', fileBuffer.length.toString());
+ headers.set('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`);
+ headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
+ headers.set('Pragma', 'no-cache');
+ headers.set('Expires', '0');
+
+ // 보안 헤더 추가
+ headers.set('X-Content-Type-Options', 'nosniff');
+ headers.set('X-Frame-Options', 'DENY');
+ headers.set('X-XSS-Protection', '1; mode=block');
+
+ // 파일 스트림 반환
+ return new NextResponse(fileBuffer, {
+ status: 200,
+ headers,
+ });
+
+ } catch (error) {
+ console.error('파일 다운로드 오류:', error);
+
+ if (error instanceof Error) {
+ if (error.message.includes('권한')) {
+ return NextResponse.json(
+ { error: error.message },
+ { status: 403 }
+ );
+ }
+ }
+
+ return NextResponse.json(
+ { error: '파일 다운로드에 실패했습니다' },
+ { status: 500 }
+ );
+ }
+}
+
+// HEAD 요청 처리 (파일 정보만 확인)
+export async function HEAD(
+ request: NextRequest,
+ { params }: { params: { projectId: string; fileId: string } }
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return new NextResponse(null, { status: 401 });
+ }
+
+ const context: FileAccessContext = {
+ userId: Number(session.user.id),
+ userDomain: session.user.domain || 'partners',
+ userEmail: session.user.email,
+ ipAddress: request.ip || request.headers.get('x-forwarded-for') || undefined,
+ userAgent: request.headers.get('user-agent') || undefined,
+ };
+
+ const fileService = new FileService();
+
+ // 파일 접근 권한 확인
+ const hasAccess = await fileService.checkFileAccess(
+ params.fileId,
+ context,
+ 'view' // HEAD 요청은 view 권한만 확인
+ );
+
+ if (!hasAccess) {
+ return new NextResponse(null, { status: 403 });
+ }
+
+ // 파일 정보 조회
+ const file = await db.query.fileItems.findFirst({
+ where: eq(fileItems.id, params.fileId),
+ });
+
+ if (!file || !file.filePath) {
+ return new NextResponse(null, { status: 404 });
+ }
+
+ const headers = new Headers();
+ headers.set('Content-Type', getMimeType(file.name, file.mimeType));
+ headers.set('Content-Length', file.size?.toString() || '0');
+ headers.set('Last-Modified', new Date(file.updatedAt).toUTCString());
+
+ return new NextResponse(null, {
+ status: 200,
+ headers,
+ });
+
+ } catch (error) {
+ console.error('HEAD 요청 오류:', error);
+ return new NextResponse(null, { status: 500 });
+ }
+}
+
+// MIME 타입 결정 헬퍼 함수
+function getMimeType(fileName: string, storedMimeType?: string | null): string {
+ // DB에 저장된 MIME 타입이 있으면 우선 사용
+ if (storedMimeType) {
+ return storedMimeType;
+ }
+
+ // 확장자 기반 MIME 타입 매핑
+ const ext = path.extname(fileName).toLowerCase().substring(1);
+ const mimeTypes: Record<string, string> = {
+ // Documents
+ 'pdf': 'application/pdf',
+ 'doc': 'application/msword',
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'xls': 'application/vnd.ms-excel',
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'ppt': 'application/vnd.ms-powerpoint',
+ 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'txt': 'text/plain',
+ 'csv': 'text/csv',
+
+ // Images
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'png': 'image/png',
+ 'gif': 'image/gif',
+ 'bmp': 'image/bmp',
+ 'webp': 'image/webp',
+ 'svg': 'image/svg+xml',
+
+ // Archives
+ 'zip': 'application/zip',
+ 'rar': 'application/x-rar-compressed',
+ '7z': 'application/x-7z-compressed',
+
+ // CAD
+ 'dwg': 'application/x-dwg',
+ 'dxf': 'application/x-dxf',
+
+ // Video
+ 'mp4': 'video/mp4',
+ 'avi': 'video/x-msvideo',
+ 'mov': 'video/quicktime',
+ 'wmv': 'video/x-ms-wmv',
+
+ // Audio
+ 'mp3': 'audio/mpeg',
+ 'wav': 'audio/wav',
+ 'ogg': 'audio/ogg',
+ };
+
+ return mimeTypes[ext] || 'application/octet-stream';
+} \ No newline at end of file